Vitrée brisée

Une faille critique dans le langage Rust, Windows trinque

De la rouille, des fenêtres, une rustine

Avatar de l'auteur
Vincent Hermann

Publié dans

LogicielSécurité

12/04/2024 5 minutes
34

Vitrée brisée

Une faille critique de sécurité a été trouvée dans la bibliothèque standard Rust. Elle ne peut être exploitée que sous Windows, à cause de la manière particulière dont le système de Microsoft découpe et interprète les arguments d’une commande.

La découverte a été faite par le chercheur en sécurité RyotaK, qui l’a signalée à l’équipe de développement de Rust. Elle est désormais référencée comme CVE-2024-24576 et a reçu la note maximale de sévérité, soit 10 sur 10. Elle a bien sûr reçu un petit nom pour l’occasion : BatBadBud.

Cette vulnérabilité est présente dans la bibliothèque standard de Rust, cette dernière n’échappant pas correctement les paramètres quand des commandes (batch ou cmd) sont invoquées.

En cas d’exploitation, le risque est que des commandes arbitraires soient transmises. Bien que la faille soit bien celle de Rust, elle remet sur le devant de la scène la manière dont Windows exécute les commandes et lit les arguments qui les accompagnent. En effet, comme expliqué par d’autres, Rust n’est pas le seul langage avec lequel il faut faire attention dans ce contexte.

Ce qui se passe sous Windows

La suite est réservée à nos abonnés.

Déjà abonné ? Se connecter

Abonnez-vous

Écrit par Vincent Hermann

Tiens, en parlant de ça :

Sommaire de l'article

Introduction

Ce qui se passe sous Windows

Le problème est présent dans d’autres langages

Pourquoi axer la communication sur Rust ?

Fermer

Commentaires (34)


Avoir axé la communication quasi seulement sur Rust et moins sur le fait que c'est Windows le problème je trouve ça malhonnête. Ainsi que la note de 10/10, il faut déjà être sur la machine et lancer la commande d'une certaine façon, aucun rapport avec par exemple le backdoor xz ou l'exécution de RCE via le réseau.
J'ai regardé l'article de blog cité pour plus de détails techniques.

Alors non, le problème ne vient pas de Windows. Le problème vient de la différence de comportement entre le monde Unix et le monde Windows (plus particulièrement, les caractères d'échappement à utiliser lors de la création d'un processus, où, sous Linux les spawn, popen, etc. utilise le backslash tandis que CreateProcess (l'API WIndows donc) utilise le caret (^).

Le fait que la même erreur ait été faite par plusieurs équipes distinctes dans plusieurs langages n'en fait pas un problème de Windows. Quand on appelle mal une API, il ne faut pas rejeter la faute sur l'API.

Des comportements Unix ont été calqués sur Windows, et après on vient dire que c'est de la faute de Windows. Le problème aurait tout aussi bien pu être dans l'autre sens, et là, personne n'aurait remis en cause Linux.

Comme expliqué dans l'article, on peut regretter que la communication soit axée sur Rust alors que la faille touche de nombreux langages. C'est juste que la faille a été découverte via Rust en premier.

fdorin

J'ai regardé l'article de blog cité pour plus de détails techniques.

Alors non, le problème ne vient pas de Windows. Le problème vient de la différence de comportement entre le monde Unix et le monde Windows (plus particulièrement, les caractères d'échappement à utiliser lors de la création d'un processus, où, sous Linux les spawn, popen, etc. utilise le backslash tandis que CreateProcess (l'API WIndows donc) utilise le caret (^).

Le fait que la même erreur ait été faite par plusieurs équipes distinctes dans plusieurs langages n'en fait pas un problème de Windows. Quand on appelle mal une API, il ne faut pas rejeter la faute sur l'API.

Des comportements Unix ont été calqués sur Windows, et après on vient dire que c'est de la faute de Windows. Le problème aurait tout aussi bien pu être dans l'autre sens, et là, personne n'aurait remis en cause Linux.

Comme expliqué dans l'article, on peut regretter que la communication soit axée sur Rust alors que la faille touche de nombreux langages. C'est juste que la faille a été découverte via Rust en premier.
Pour avoir eu un souci similaire en Java (plus exactement une issue reportée par un outil de sécurité), c'est surtout qu'il y a une différence fondamentale entre Windows et Linux : en C, tu as des commandes comme execvp dont la signature passe par un tableau de paramètres.

Mais CreateProcess fonctionne différemment : https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw
[in, out, optional] lpCommandLine
The command line to be executed.
The maximum length of this string is 32,767 characters, including the Unicode terminating null character. If lpApplicationName is NULL, the module name portion of lpCommandLine is limited to MAX_PATH characters.


On passe une ligne de commande - pas un exécutable et ses paramètres - , ce qui introduit tout un tas de problèmes :

- celui des espaces
- celui des caractères spéciaux, qui t'en qu'à faire sont différents de Windows à Linux
- ... une limite de 32767 caractères

Pour reprendre Java, et je suppose que Rust fait pareil, on utilise bien des tableaux dans le code du langage : https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/lang/ProcessBuilder.java#L1126

Côté Windows, on voit que Java recalcule une ligne de commande : https://github.com/openjdk/jdk/blob/master/src/java.base/windows/classes/java/lang/ProcessImpl.java#L453

Côté Linux c'est (bien) différent : on passe des tableaux, que des tableaux, rien que des tableaux avec la seule contrainte du caractère NUL en fin de chaîne.

- https://github.com/openjdk/jdk/blob/master/src/java.base/unix/classes/java/lang/ProcessImpl.java#L158
- https://github.com/openjdk/jdk/blob/master/src/java.base/unix/native/libjava/ProcessImpl_md.c#L655

Et pour le coup, si Windows ne fournit pas l'API pour éviter (car il s'agit de ça au final) de prendre les paramètres et de recréer une ligne de commande, alors oui Windows est responsable de la situation.

(si les liens ci-dessus référencent Java 21, ça existe depuis au moins Java 8, donc ce problème Windows qui concerne Rust, Java, etc.... est là depuis au moins 2013 :))

Baldurien

Pour avoir eu un souci similaire en Java (plus exactement une issue reportée par un outil de sécurité), c'est surtout qu'il y a une différence fondamentale entre Windows et Linux : en C, tu as des commandes comme execvp dont la signature passe par un tableau de paramètres.

Mais CreateProcess fonctionne différemment : https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw
[in, out, optional] lpCommandLine
The command line to be executed.
The maximum length of this string is 32,767 characters, including the Unicode terminating null character. If lpApplicationName is NULL, the module name portion of lpCommandLine is limited to MAX_PATH characters.


On passe une ligne de commande - pas un exécutable et ses paramètres - , ce qui introduit tout un tas de problèmes :

- celui des espaces
- celui des caractères spéciaux, qui t'en qu'à faire sont différents de Windows à Linux
- ... une limite de 32767 caractères

Pour reprendre Java, et je suppose que Rust fait pareil, on utilise bien des tableaux dans le code du langage : https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/lang/ProcessBuilder.java#L1126

Côté Windows, on voit que Java recalcule une ligne de commande : https://github.com/openjdk/jdk/blob/master/src/java.base/windows/classes/java/lang/ProcessImpl.java#L453

Côté Linux c'est (bien) différent : on passe des tableaux, que des tableaux, rien que des tableaux avec la seule contrainte du caractère NUL en fin de chaîne.

- https://github.com/openjdk/jdk/blob/master/src/java.base/unix/classes/java/lang/ProcessImpl.java#L158
- https://github.com/openjdk/jdk/blob/master/src/java.base/unix/native/libjava/ProcessImpl_md.c#L655

Et pour le coup, si Windows ne fournit pas l'API pour éviter (car il s'agit de ça au final) de prendre les paramètres et de recréer une ligne de commande, alors oui Windows est responsable de la situation.

(si les liens ci-dessus référencent Java 21, ça existe depuis au moins Java 8, donc ce problème Windows qui concerne Rust, Java, etc.... est là depuis au moins 2013 :))
Et pour le coup, si Windows ne fournit pas l'API pour éviter (car il s'agit de ça au final) de prendre les paramètres et de recréer une ligne de commande, alors oui Windows est responsable de la situation.


Ben non, pas d'accord. En quoi c'est une situation à éviter ? Ce sont des choix, différents (ce que je souligne depuis le début), et c'est de cette différence que vient le problème.

Et les deux ont aussi des différences en dehors du caractère d'échappement. Il est beaucoup plus simple avec l'approche de Windows de vérifier la taille max des arguments de la ligne de commande (alors que sous linux, il faut la reconstituer, sans oublier de compter les espaces !). Car oui, il y a une taille max !

De même, l'approche utilisée par Windows préserve les espaces. S'il y a besoin de 3 espaces entre deux arguments, c'est possible de le faire sous Windows, pas sous Linux.

Encore une fois, il n'y a pas une approche qui soit meilleure qu'une autre. Ce sont juste 2 approches différentes.

fdorin

Et pour le coup, si Windows ne fournit pas l'API pour éviter (car il s'agit de ça au final) de prendre les paramètres et de recréer une ligne de commande, alors oui Windows est responsable de la situation.


Ben non, pas d'accord. En quoi c'est une situation à éviter ? Ce sont des choix, différents (ce que je souligne depuis le début), et c'est de cette différence que vient le problème.

Et les deux ont aussi des différences en dehors du caractère d'échappement. Il est beaucoup plus simple avec l'approche de Windows de vérifier la taille max des arguments de la ligne de commande (alors que sous linux, il faut la reconstituer, sans oublier de compter les espaces !). Car oui, il y a une taille max !

De même, l'approche utilisée par Windows préserve les espaces. S'il y a besoin de 3 espaces entre deux arguments, c'est possible de le faire sous Windows, pas sous Linux.

Encore une fois, il n'y a pas une approche qui soit meilleure qu'une autre. Ce sont juste 2 approches différentes.
Ben non, pas d'accord. En quoi c'est une situation à éviter ? Ce sont des choix, différents (ce que je souligne depuis le début), et c'est de cette différence que vient le problème.


Parce que tu fournis une ligne de commande à analyser (parser) plutôt que de fournir le résultat de cette analyse ? Résultat que tu connais car au final, d'un point de vue programme tu connais les paramètres du programme que tu appelles.
Cela passe pour un shell, où l'utilisateur entre des commandes, mais pas pour des APIs bas niveau.

Et je ne vois pas le souci d'espaces dans la façon Linux : tu passes juste un tableau de chaîne de caractères (terminées par le caractère NUL). Le fait de vérifier la taille max de l'ensemble n'est pas plus coûteux : vu la différence de temps de lancement entre Linux et Windows (plus lent), ce n'est certainement pas ça le plus coûteux !
Encore une fois, il n'y a pas une approche qui soit meilleure qu'une autre. Ce sont juste 2 approches différentes.


Si justement : dans le cas présent, il s'agit d’exécuter un programme depuis un autre programme avec les paramètres connus.

D'une part, la façon de faire de WIndows introduit des problèmes d'analyses vu que les règles sont difficiles à utiliser (je dis ça d'expérience de batch), pas documentée sur la doc de CreateProcess (pas trouvé sur la page de lien ou d’explication), d'autre part ça introduit des failles de sécurité du fait de cette complexité et de cas limites (notamment sur les caractères ").

Or, il s'agit bien de ça : Rust a une faille critique à cause de cette façon de faire qui nécessite un travail compliqué, mal compris et pour rien

Baldurien

Ben non, pas d'accord. En quoi c'est une situation à éviter ? Ce sont des choix, différents (ce que je souligne depuis le début), et c'est de cette différence que vient le problème.


Parce que tu fournis une ligne de commande à analyser (parser) plutôt que de fournir le résultat de cette analyse ? Résultat que tu connais car au final, d'un point de vue programme tu connais les paramètres du programme que tu appelles.
Cela passe pour un shell, où l'utilisateur entre des commandes, mais pas pour des APIs bas niveau.

Et je ne vois pas le souci d'espaces dans la façon Linux : tu passes juste un tableau de chaîne de caractères (terminées par le caractère NUL). Le fait de vérifier la taille max de l'ensemble n'est pas plus coûteux : vu la différence de temps de lancement entre Linux et Windows (plus lent), ce n'est certainement pas ça le plus coûteux !
Encore une fois, il n'y a pas une approche qui soit meilleure qu'une autre. Ce sont juste 2 approches différentes.


Si justement : dans le cas présent, il s'agit d’exécuter un programme depuis un autre programme avec les paramètres connus.

D'une part, la façon de faire de WIndows introduit des problèmes d'analyses vu que les règles sont difficiles à utiliser (je dis ça d'expérience de batch), pas documentée sur la doc de CreateProcess (pas trouvé sur la page de lien ou d’explication), d'autre part ça introduit des failles de sécurité du fait de cette complexité et de cas limites (notamment sur les caractères ").

Or, il s'agit bien de ça : Rust a une faille critique à cause de cette façon de faire qui nécessite un travail compliqué, mal compris et pour rien
Parce que tu fournis une ligne de commande à analyser (parser) plutôt que de fournir le résultat de cette analyse ? Résultat que tu connais car au final, d'un point de vue programme tu connais les paramètres du programme que tu appelles.

Cela passe pour un shell, où l'utilisateur entre des commandes, mais pas pour des APIs bas niveau.

La question est de savoir si les paramètres du ligne de commande sont nécessaires au système d'exploitation ou pas. La réponse est non. L'OS à juste besoin de savoir le programme à lancer, et les paramètres à passer, mais n'a pas à connaitre la structure des paramètres (tableau ou ligne).

Sous Linux, le choix a été fait d'organiser les paramètres sous forme de tableau. Sous Windows, c'est une chaine de caractère. Les deux sont tout à fait viable et ont de subtiles différences.
Et je ne vois pas le souci d'espaces dans la façon Linux : tu passes juste un tableau de chaîne de caractères (terminées par le caractère NUL). Le fait de vérifier la taille max de l'ensemble n'est pas plus coûteux : vu la différence de temps de lancement entre Linux et Windows (plus lent), ce n'est certainement pas ça le plus coûteux !


Je ne parlais de la complexité en terme de temps de calcul, mais de la complexité d'avoir un algorithme correcte. Et j'avoue ne pas trop voir en quoi le temps de lancement vient jouer un rôle ici. On parle des paramètres des programmes. Cela n'a strictement rien à voir.
D'une part, la façon de faire de WIndows introduit des problèmes d'analyses vu que les règles sont difficiles à utiliser (je dis ça d'expérience de batch),


Jusqu'au jour où on va se rendre compte que linux a en fait potentiellement le même problème, car quand un script est exécuté, l'interpréteur est déterminé via le shell bang. Chaque interpréteur peut avoir des règles d'échappements différents. Du coup, là aussi, on accusera Windows ?
Or, il s'agit bien de ça : Rust a une faille critique à cause de cette façon de faire qui nécessite un travail compliqué, mal compris et pour rien


Pas pour rien non. Quand les systèmes sont différents, il faut bien palier les différences. C'est le rôle du pattern adapteur ou d'un wrapper en programmation. Mais parfois c'est compliqué oui. Comme ici.

fdorin

Parce que tu fournis une ligne de commande à analyser (parser) plutôt que de fournir le résultat de cette analyse ? Résultat que tu connais car au final, d'un point de vue programme tu connais les paramètres du programme que tu appelles.

Cela passe pour un shell, où l'utilisateur entre des commandes, mais pas pour des APIs bas niveau.

La question est de savoir si les paramètres du ligne de commande sont nécessaires au système d'exploitation ou pas. La réponse est non. L'OS à juste besoin de savoir le programme à lancer, et les paramètres à passer, mais n'a pas à connaitre la structure des paramètres (tableau ou ligne).

Sous Linux, le choix a été fait d'organiser les paramètres sous forme de tableau. Sous Windows, c'est une chaine de caractère. Les deux sont tout à fait viable et ont de subtiles différences.
Et je ne vois pas le souci d'espaces dans la façon Linux : tu passes juste un tableau de chaîne de caractères (terminées par le caractère NUL). Le fait de vérifier la taille max de l'ensemble n'est pas plus coûteux : vu la différence de temps de lancement entre Linux et Windows (plus lent), ce n'est certainement pas ça le plus coûteux !


Je ne parlais de la complexité en terme de temps de calcul, mais de la complexité d'avoir un algorithme correcte. Et j'avoue ne pas trop voir en quoi le temps de lancement vient jouer un rôle ici. On parle des paramètres des programmes. Cela n'a strictement rien à voir.
D'une part, la façon de faire de WIndows introduit des problèmes d'analyses vu que les règles sont difficiles à utiliser (je dis ça d'expérience de batch),


Jusqu'au jour où on va se rendre compte que linux a en fait potentiellement le même problème, car quand un script est exécuté, l'interpréteur est déterminé via le shell bang. Chaque interpréteur peut avoir des règles d'échappements différents. Du coup, là aussi, on accusera Windows ?
Or, il s'agit bien de ça : Rust a une faille critique à cause de cette façon de faire qui nécessite un travail compliqué, mal compris et pour rien


Pas pour rien non. Quand les systèmes sont différents, il faut bien palier les différences. C'est le rôle du pattern adapteur ou d'un wrapper en programmation. Mais parfois c'est compliqué oui. Comme ici.
La question est de savoir si les paramètres du ligne de commande sont nécessaires au système d'exploitation ou pas. La réponse est non. L'OS à juste besoin de savoir le programme à lancer, et les paramètres à passer, mais n'a pas à connaitre la structure des paramètres (tableau ou ligne).
Sous Linux, le choix a été fait d'organiser les paramètres sous forme de tableau. Sous Windows, c'est une chaine de caractère. Les deux sont tout à fait viable et ont de subtiles différences.


Analyser une ligne de commande pour la traduire en paramètres, n'est pas le souci en soit : le souci c'est que cette analyse ne sert à rien dans le cas d'une API surtout quand tous les programmes C/C++ passent par une méthode main qui prends le nombre de paramètres (argc) et ces paramètres (argv).

C'est le cas autant sous Windows que sous Linux.

D'ailleurs, j'ai aussi trouvé la doc qui explique ce que fait Windows pour transformer une chaîne en paramètres :
https://learn.microsoft.com/en-us/cpp/cpp/main-function-command-line-args?view=msvc-170#the-main-function-signature
Je ne parlais de la complexité en terme de temps de calcul, mais de la complexité d'avoir un algorithme correcte. Et j'avoue ne pas trop voir en quoi le temps de lancement vient jouer un rôle ici. On parle des paramètres des programmes. Cela n'a strictement rien à voir.


Justement : l'algorithme est simple.
Calculer la somme des tailles de chaîne d'un tableau c'est très facile.
Rien ne justifie la méthode de Windows, certainement pas la complexité algorithme ou le temps d’exécution de l'algorithme.
Jusqu'au jour où on va se rendre compte que linux a en fait potentiellement le même problème, car quand un script est exécuté, l'interpréteur est déterminé via le shell bang. Chaque interpréteur peut avoir des règles d'échappements différents. Du coup, là aussi, on accusera Windows ?


Sauf que les règles du shebang sont (probablement) entièrement liées au système : j'imagine que sous Linux, il commence déjà par voir à quoi il a affaire, programme ELF ou simple fichier avec shebang, puis il fait son travail : ce n'est pas l'application qui doit faire ce travail.

Si par exemple le caractères 0x36 doit être interdit, c'est le système que tu patches : pas chaque programme souhaitant lancer un programme.

Or, c'est justement ça le cœur du problème ici : Rust doit gérer ça, Java doit gérer ça, ...
Pas pour rien non. Quand les systèmes sont différents, il faut bien palier les différences. C'est le rôle du pattern adapteur ou d'un wrapper en programmation. Mais parfois c'est compliqué oui. Comme ici.


Certes, mais tout le débat ici c'est justement que cette différence n'a aucun sens dans le cas présent et ne fait qu'introduire des failles de sécurité pour rien...

Si on veut parler d'un adapter/wrapper, probablement que tous les langages utilisant CreateProcess devraient passer par une API commune, cela réduirait les surfaces d'attaque.

Baldurien

La question est de savoir si les paramètres du ligne de commande sont nécessaires au système d'exploitation ou pas. La réponse est non. L'OS à juste besoin de savoir le programme à lancer, et les paramètres à passer, mais n'a pas à connaitre la structure des paramètres (tableau ou ligne).
Sous Linux, le choix a été fait d'organiser les paramètres sous forme de tableau. Sous Windows, c'est une chaine de caractère. Les deux sont tout à fait viable et ont de subtiles différences.


Analyser une ligne de commande pour la traduire en paramètres, n'est pas le souci en soit : le souci c'est que cette analyse ne sert à rien dans le cas d'une API surtout quand tous les programmes C/C++ passent par une méthode main qui prends le nombre de paramètres (argc) et ces paramètres (argv).

C'est le cas autant sous Windows que sous Linux.

D'ailleurs, j'ai aussi trouvé la doc qui explique ce que fait Windows pour transformer une chaîne en paramètres :
https://learn.microsoft.com/en-us/cpp/cpp/main-function-command-line-args?view=msvc-170#the-main-function-signature
Je ne parlais de la complexité en terme de temps de calcul, mais de la complexité d'avoir un algorithme correcte. Et j'avoue ne pas trop voir en quoi le temps de lancement vient jouer un rôle ici. On parle des paramètres des programmes. Cela n'a strictement rien à voir.


Justement : l'algorithme est simple.
Calculer la somme des tailles de chaîne d'un tableau c'est très facile.
Rien ne justifie la méthode de Windows, certainement pas la complexité algorithme ou le temps d’exécution de l'algorithme.
Jusqu'au jour où on va se rendre compte que linux a en fait potentiellement le même problème, car quand un script est exécuté, l'interpréteur est déterminé via le shell bang. Chaque interpréteur peut avoir des règles d'échappements différents. Du coup, là aussi, on accusera Windows ?


Sauf que les règles du shebang sont (probablement) entièrement liées au système : j'imagine que sous Linux, il commence déjà par voir à quoi il a affaire, programme ELF ou simple fichier avec shebang, puis il fait son travail : ce n'est pas l'application qui doit faire ce travail.

Si par exemple le caractères 0x36 doit être interdit, c'est le système que tu patches : pas chaque programme souhaitant lancer un programme.

Or, c'est justement ça le cœur du problème ici : Rust doit gérer ça, Java doit gérer ça, ...
Pas pour rien non. Quand les systèmes sont différents, il faut bien palier les différences. C'est le rôle du pattern adapteur ou d'un wrapper en programmation. Mais parfois c'est compliqué oui. Comme ici.


Certes, mais tout le débat ici c'est justement que cette différence n'a aucun sens dans le cas présent et ne fait qu'introduire des failles de sécurité pour rien...

Si on veut parler d'un adapter/wrapper, probablement que tous les langages utilisant CreateProcess devraient passer par une API commune, cela réduirait les surfaces d'attaque.
Et pour le shebang, c'est au final assez bien décrit : https://www.man7.org/linux/man-pages/man2/execve.2.html

Et ça me donne l'impression qu'au final, il refait un exeve en ayant juste changer le programme pour celui du shebang et ajouter le paramètre optionnel en début du tableau = pas de ligne de commandes.

Baldurien

La question est de savoir si les paramètres du ligne de commande sont nécessaires au système d'exploitation ou pas. La réponse est non. L'OS à juste besoin de savoir le programme à lancer, et les paramètres à passer, mais n'a pas à connaitre la structure des paramètres (tableau ou ligne).
Sous Linux, le choix a été fait d'organiser les paramètres sous forme de tableau. Sous Windows, c'est une chaine de caractère. Les deux sont tout à fait viable et ont de subtiles différences.


Analyser une ligne de commande pour la traduire en paramètres, n'est pas le souci en soit : le souci c'est que cette analyse ne sert à rien dans le cas d'une API surtout quand tous les programmes C/C++ passent par une méthode main qui prends le nombre de paramètres (argc) et ces paramètres (argv).

C'est le cas autant sous Windows que sous Linux.

D'ailleurs, j'ai aussi trouvé la doc qui explique ce que fait Windows pour transformer une chaîne en paramètres :
https://learn.microsoft.com/en-us/cpp/cpp/main-function-command-line-args?view=msvc-170#the-main-function-signature
Je ne parlais de la complexité en terme de temps de calcul, mais de la complexité d'avoir un algorithme correcte. Et j'avoue ne pas trop voir en quoi le temps de lancement vient jouer un rôle ici. On parle des paramètres des programmes. Cela n'a strictement rien à voir.


Justement : l'algorithme est simple.
Calculer la somme des tailles de chaîne d'un tableau c'est très facile.
Rien ne justifie la méthode de Windows, certainement pas la complexité algorithme ou le temps d’exécution de l'algorithme.
Jusqu'au jour où on va se rendre compte que linux a en fait potentiellement le même problème, car quand un script est exécuté, l'interpréteur est déterminé via le shell bang. Chaque interpréteur peut avoir des règles d'échappements différents. Du coup, là aussi, on accusera Windows ?


Sauf que les règles du shebang sont (probablement) entièrement liées au système : j'imagine que sous Linux, il commence déjà par voir à quoi il a affaire, programme ELF ou simple fichier avec shebang, puis il fait son travail : ce n'est pas l'application qui doit faire ce travail.

Si par exemple le caractères 0x36 doit être interdit, c'est le système que tu patches : pas chaque programme souhaitant lancer un programme.

Or, c'est justement ça le cœur du problème ici : Rust doit gérer ça, Java doit gérer ça, ...
Pas pour rien non. Quand les systèmes sont différents, il faut bien palier les différences. C'est le rôle du pattern adapteur ou d'un wrapper en programmation. Mais parfois c'est compliqué oui. Comme ici.


Certes, mais tout le débat ici c'est justement que cette différence n'a aucun sens dans le cas présent et ne fait qu'introduire des failles de sécurité pour rien...

Si on veut parler d'un adapter/wrapper, probablement que tous les langages utilisant CreateProcess devraient passer par une API commune, cela réduirait les surfaces d'attaque.
Il n'y a pas que les programmes C sous Windows... Le "point d'entrée" d'une application Win32 est WinMain...

https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-winmain

Pas d'argc/argv...

fdorin

Et pour le coup, si Windows ne fournit pas l'API pour éviter (car il s'agit de ça au final) de prendre les paramètres et de recréer une ligne de commande, alors oui Windows est responsable de la situation.


Ben non, pas d'accord. En quoi c'est une situation à éviter ? Ce sont des choix, différents (ce que je souligne depuis le début), et c'est de cette différence que vient le problème.

Et les deux ont aussi des différences en dehors du caractère d'échappement. Il est beaucoup plus simple avec l'approche de Windows de vérifier la taille max des arguments de la ligne de commande (alors que sous linux, il faut la reconstituer, sans oublier de compter les espaces !). Car oui, il y a une taille max !

De même, l'approche utilisée par Windows préserve les espaces. S'il y a besoin de 3 espaces entre deux arguments, c'est possible de le faire sous Windows, pas sous Linux.

Encore une fois, il n'y a pas une approche qui soit meilleure qu'une autre. Ce sont juste 2 approches différentes.
Le problème c'est que CreateProcess à deux comportement différent suivant que le fichier qu'il exécute est un exécutable ou un fichier de commande et que le second cas n'est pas documenté.

Dans le premier cas les paramètre sont simplement passés comme arguments à l’exécutable, un simple échappement suffit, alors que dans le second cas les paramètre sont interprétés comme une ligne de commande ce qui n'est clairement pas ce qu'attendent les utilisateurs, et qui est difficilement échappable.

On pourrait au moins excuser ce comportement étrange s'il était documenté.

Uther

Le problème c'est que CreateProcess à deux comportement différent suivant que le fichier qu'il exécute est un exécutable ou un fichier de commande et que le second cas n'est pas documenté.

Dans le premier cas les paramètre sont simplement passés comme arguments à l’exécutable, un simple échappement suffit, alors que dans le second cas les paramètre sont interprétés comme une ligne de commande ce qui n'est clairement pas ce qu'attendent les utilisateurs, et qui est difficilement échappable.

On pourrait au moins excuser ce comportement étrange s'il était documenté.
On est d'accord sur un manque de documentation. Ce n'est pas pour rien que plusieurs équipes ont fait la même erreur.

Cela n'en constitue pas pour autant une faille dans l'API Windows.

fdorin

On est d'accord sur un manque de documentation. Ce n'est pas pour rien que plusieurs équipes ont fait la même erreur.

Cela n'en constitue pas pour autant une faille dans l'API Windows.
Si tu parts du principe que l'utilisateur est responsable de la sécurité de ses paramètres mais qu'en même temps c'est l'implémentation qui fait foi et pas la documentation, en effet rien ne sera jamais une faille dans l'API.

Il n’empêche que, le fait que CreateProcess traite les arguments comme on traite une ligne de commande n'est ni le comportement auquel on s'attend, ni ce qui est documenté, et il en résulte une possibilité d'exploitation.

Dire si c'est une faille ou pas, c'est jouer sur les mots. En tout cas, ça en a les symptômes et ça devrait vraiment être corrigé, au minimum par une mise à jour de la documentation.
Modifié le 13/04/2024 à 10h38

Historique des modifications :

Posté le 13/04/2024 à 10h31


Si tu parts du principe que l'utilisateur est responsable de la sécurité de ses paramètres mais qu'en même temps c'est l'implémentation qui fait foi et pas la documentation, en effet rien ne sera jamais une faille dans l'API.

Il n’empêche que pour moi, traiter les arguments de CreateProcess comme on traite une ligne de commande n'est clairement pas le comportement auquel on attend, d'autant plus si ça n'est pas documenté, et il en résulte une possibilité d'exploitation. Dire que ça n'est pas une faille, c'est jouer sur les mots : ça en a les symptômes.

Posté le 13/04/2024 à 10h32


Si tu parts du principe que l'utilisateur est responsable de la sécurité de ses paramètres mais qu'en même temps c'est l'implémentation qui fait foi et pas la documentation, en effet rien ne sera jamais une faille dans l'API.

Il n’empêche que pour moi, traiter les arguments de CreateProcess comme on traite une ligne de commande n'est clairement pas le comportement auquel on s'attend, d'autant plus si ça n'est pas documenté, et il en résulte une possibilité d'exploitation. Dire que ça n'est pas une faille, c'est jouer sur les mots : ça en a les symptômes.

Posté le 13/04/2024 à 10h34


Si tu parts du principe que l'utilisateur est responsable de la sécurité de ses paramètres mais qu'en même temps c'est l'implémentation qui fait foi et pas la documentation, en effet rien ne sera jamais une faille dans l'API.

Il n’empêche que pour moi, traiter les arguments de CreateProcess comme on traite une ligne de commande n'est clairement pas le comportement auquel on s'attend, d'autant plus si ça n'est pas documenté, et il en résulte une possibilité d'exploitation. Dire si c'est une faille ou pas, c'est jouer sur les mots. En tout cas, ça en a les symptômes et devrait être corrigé au minimum par une mise à jour de la documentation.

Uther

Le problème c'est que CreateProcess à deux comportement différent suivant que le fichier qu'il exécute est un exécutable ou un fichier de commande et que le second cas n'est pas documenté.

Dans le premier cas les paramètre sont simplement passés comme arguments à l’exécutable, un simple échappement suffit, alors que dans le second cas les paramètre sont interprétés comme une ligne de commande ce qui n'est clairement pas ce qu'attendent les utilisateurs, et qui est difficilement échappable.

On pourrait au moins excuser ce comportement étrange s'il était documenté.
Pour ma part je ne savais même pas que CreateProcess prenait les .bat en charge. "instinctivement" si besoin je me tournerais vers ShellExecute.

fdorin

J'ai regardé l'article de blog cité pour plus de détails techniques.

Alors non, le problème ne vient pas de Windows. Le problème vient de la différence de comportement entre le monde Unix et le monde Windows (plus particulièrement, les caractères d'échappement à utiliser lors de la création d'un processus, où, sous Linux les spawn, popen, etc. utilise le backslash tandis que CreateProcess (l'API WIndows donc) utilise le caret (^).

Le fait que la même erreur ait été faite par plusieurs équipes distinctes dans plusieurs langages n'en fait pas un problème de Windows. Quand on appelle mal une API, il ne faut pas rejeter la faute sur l'API.

Des comportements Unix ont été calqués sur Windows, et après on vient dire que c'est de la faute de Windows. Le problème aurait tout aussi bien pu être dans l'autre sens, et là, personne n'aurait remis en cause Linux.

Comme expliqué dans l'article, on peut regretter que la communication soit axée sur Rust alors que la faille touche de nombreux langages. C'est juste que la faille a été découverte via Rust en premier.
Le fait que la même erreur ait été faite par plusieurs équipes distinctes dans plusieurs langages n'en fait pas un problème de Windows. Quand on appelle mal une API, il ne faut pas rejeter la faute sur l'API.


Vraiment ? L'argument serait valide pour un petit nombre de personnes, ou sur un langage unique.
Ce n'est pas le cas ici.

Le standard sur lequel s'appuie Rust est celui de GNU C, pas "Linux" (qui est un noyau). Rien de bien nouveau, et qui à défaut d'être une norme, est un standard d'interopérabilité.
Qui plus, un vieux standard : on ne parle pas de dernière fraîcheur.

La vieillerie cmd.exe contient une manière très particulière (comme personne d'autre) de fonctionner, et ce depuis donc bien trop longtemps.
Une n-ème démonstration que l'habitude de M$ de produire du non-standard crée un/des risque(s) pour les utilisateurs de leurs produits.

Les langages doivent donc adopter un comportement particulier pour ce système d'exploitation particulier. D'aucuns dirait que c'est bien le système d'exploitation le problème.

Cf. https://blog.rust-lang.org/2024/04/09/cve-2024-24576.html
Modifié le 12/04/2024 à 20h48

Historique des modifications :

Posté le 12/04/2024 à 20h46


Le fait que la même erreur ait été faite par plusieurs équipes distinctes dans plusieurs langages n'en fait pas un problème de Windows. Quand on appelle mal une API, il ne faut pas rejeter la faute sur l'API.


Vraiment ? L'argument serait valide pour un petit nombre de personnes, ou sur un langage unique.

Le standard sur lequel s'appuie Rust est celui de GNU C, pas "Linux" (qui est un noyau). Rien de bien nouveau, et à défaut d'être une norme, est un standard d'interopérabilité.
Qui plus, un vieux standard : on ne parle pas de dernière fraîcheur.

La vieillerie cmd.exe contient une manière très particulière (comme _personne_ d'autre) de fonctionner, et ce depuis donc bien trop longtemps.
Une n-ème démonstration que l'habitude de M$ de produire du non-standard crée un/des risque(s) pour les utilisateurs de leurs produits.

Les langages doivent donc adopter un comportement particulier pour ce système d'exploitation particulier. D'aucuns dirait que c'est bien le système d'exploitation le problème.

Cf. https://blog.rust-lang.org/2024/04/09/cve-2024-24576.html

Posté le 12/04/2024 à 20h47


Le fait que la même erreur ait été faite par plusieurs équipes distinctes dans plusieurs langages n'en fait pas un problème de Windows. Quand on appelle mal une API, il ne faut pas rejeter la faute sur l'API.


Vraiment ? L'argument serait valide pour un petit nombre de personnes, ou sur un langage unique.
Ce n'est pas le cas ici.

Le standard sur lequel s'appuie Rust est celui de GNU C, pas "Linux" (qui est un noyau). Rien de bien nouveau, et à défaut d'être une norme, est un standard d'interopérabilité.
Qui plus, un vieux standard : on ne parle pas de dernière fraîcheur.

La vieillerie cmd.exe contient une manière très particulière (comme _personne_ d'autre) de fonctionner, et ce depuis donc bien trop longtemps.
Une n-ème démonstration que l'habitude de M$ de produire du non-standard crée un/des risque(s) pour les utilisateurs de leurs produits.

Les langages doivent donc adopter un comportement particulier pour ce système d'exploitation particulier. D'aucuns dirait que c'est bien le système d'exploitation le problème.

Cf. https://blog.rust-lang.org/2024/04/09/cve-2024-24576.html

Posté le 12/04/2024 à 20h47


Le fait que la même erreur ait été faite par plusieurs équipes distinctes dans plusieurs langages n'en fait pas un problème de Windows. Quand on appelle mal une API, il ne faut pas rejeter la faute sur l'API.


Vraiment ? L'argument serait valide pour un petit nombre de personnes, ou sur un langage unique.
Ce n'est pas le cas ici.

Le standard sur lequel s'appuie Rust est celui de GNU C, pas "Linux" (qui est un noyau). Rien de bien nouveau, et qui à défaut d'être une norme, est un standard d'interopérabilité.
Qui plus, un vieux standard : on ne parle pas de dernière fraîcheur.

La vieillerie cmd.exe contient une manière très particulière (comme _personne_ d'autre) de fonctionner, et ce depuis donc bien trop longtemps.
Une n-ème démonstration que l'habitude de M$ de produire du non-standard crée un/des risque(s) pour les utilisateurs de leurs produits.

Les langages doivent donc adopter un comportement particulier pour ce système d'exploitation particulier. D'aucuns dirait que c'est bien le système d'exploitation le problème.

Cf. https://blog.rust-lang.org/2024/04/09/cve-2024-24576.html

Berbe

Le fait que la même erreur ait été faite par plusieurs équipes distinctes dans plusieurs langages n'en fait pas un problème de Windows. Quand on appelle mal une API, il ne faut pas rejeter la faute sur l'API.


Vraiment ? L'argument serait valide pour un petit nombre de personnes, ou sur un langage unique.
Ce n'est pas le cas ici.

Le standard sur lequel s'appuie Rust est celui de GNU C, pas "Linux" (qui est un noyau). Rien de bien nouveau, et qui à défaut d'être une norme, est un standard d'interopérabilité.
Qui plus, un vieux standard : on ne parle pas de dernière fraîcheur.

La vieillerie cmd.exe contient une manière très particulière (comme personne d'autre) de fonctionner, et ce depuis donc bien trop longtemps.
Une n-ème démonstration que l'habitude de M$ de produire du non-standard crée un/des risque(s) pour les utilisateurs de leurs produits.

Les langages doivent donc adopter un comportement particulier pour ce système d'exploitation particulier. D'aucuns dirait que c'est bien le système d'exploitation le problème.

Cf. https://blog.rust-lang.org/2024/04/09/cve-2024-24576.html
Le standard sur lequel s'appuie Rust est celui de GNU C, pas "Linux" (qui est un noyau). Rien de bien nouveau, et qui à défaut d'être une norme, est un standard d'interopérabilité.

Qui plus, un vieux standard : on ne parle pas de dernière fraîcheur.

Pour être plus précis, le standard derrière s'appelle Posix (et pas GNU C). Sauf que l'API Windows existe 1985, POSIX depuis 1988. Windows ne respecte donc pas un standard qui n'existait tout simplement pas l'époque des premières versions du système d'exploitation.
Les langages doivent donc adopter un comportement particulier pour ce système d'exploitation particulier. D'aucuns dirait que c'est bien le système d'exploitation le problème.


C'est le principe de la bibliothèque standard de chaque langage. Quand tu as un langage dont l'API est proche de Posix, c'est plus simple à développer sur un système Posix que sur un système non Posix.

Le fait que plusieurs personnes se sont cassées les dents et qu'il y ait une faille à ce sujet aujourd'hui montre 2 choses :
- écrire une implémentation d'une API standard, ce n'est pas quelque chose de trivial
- un manque de documentation de l'API Windows.

Mais cela ne signifie pas que l'API présent une faille comme beaucoup le pense.

Le problème aujourd'hui touche uniquement Windows car :
- les systèmes à base de Linux (distribution classique, Android, etc.) respectent plus ou moins le standard POSIX
- les BSD respectent plus ou moins POSIX
- MacOS (basé sur BSD) respect plus ou moins POSIX

En fait, Windows est le seul système d'exploitation "grand publique" qui ne soit pas Posix de nos jours.
Une n-ème démonstration que l'habitude de M$ de produire du non-standard crée un/des risque(s) pour les utilisateurs de leurs produits


Comme dit plus haut, le standard n'existait pas à l'époque des premières versions de Windows.

fdorin

Le standard sur lequel s'appuie Rust est celui de GNU C, pas "Linux" (qui est un noyau). Rien de bien nouveau, et qui à défaut d'être une norme, est un standard d'interopérabilité.

Qui plus, un vieux standard : on ne parle pas de dernière fraîcheur.

Pour être plus précis, le standard derrière s'appelle Posix (et pas GNU C). Sauf que l'API Windows existe 1985, POSIX depuis 1988. Windows ne respecte donc pas un standard qui n'existait tout simplement pas l'époque des premières versions du système d'exploitation.
Les langages doivent donc adopter un comportement particulier pour ce système d'exploitation particulier. D'aucuns dirait que c'est bien le système d'exploitation le problème.


C'est le principe de la bibliothèque standard de chaque langage. Quand tu as un langage dont l'API est proche de Posix, c'est plus simple à développer sur un système Posix que sur un système non Posix.

Le fait que plusieurs personnes se sont cassées les dents et qu'il y ait une faille à ce sujet aujourd'hui montre 2 choses :
- écrire une implémentation d'une API standard, ce n'est pas quelque chose de trivial
- un manque de documentation de l'API Windows.

Mais cela ne signifie pas que l'API présent une faille comme beaucoup le pense.

Le problème aujourd'hui touche uniquement Windows car :
- les systèmes à base de Linux (distribution classique, Android, etc.) respectent plus ou moins le standard POSIX
- les BSD respectent plus ou moins POSIX
- MacOS (basé sur BSD) respect plus ou moins POSIX

En fait, Windows est le seul système d'exploitation "grand publique" qui ne soit pas Posix de nos jours.
Une n-ème démonstration que l'habitude de M$ de produire du non-standard crée un/des risque(s) pour les utilisateurs de leurs produits


Comme dit plus haut, le standard n'existait pas à l'époque des premières versions de Windows.
Le problème est que l'API Windows est fondamentalement limitée et ne permet pas de passer une liste d'arguments à un programme, uniquement une chaîne de caractères qui sera "la commande", ce qui implique tout un tas de problèmes.

Oui Rust, Java & co doivent trouver une manière correcte pour contourner cette limitation de Windows. Mais pas seulement, c'est a peu près certain qu'on trouve dans le monde des dizaines de milliers de failles à cause de cette limitation problématique et difficilement compatible avec la manière dont la fonction main est spécifiée dans la plupart des langages (les argument sont une liste de string, pas une string)
Encore une fois, il n'y a pas une approche qui soit meilleure qu'une autre.

Si, il y a une approche correcte, qui maintient la sémantique de la liste d'arguments sur toute la ligne, de la commande ou du script jusqu'à la fonction main du programme, et une approche fondamentalement problématique et qui encourage les failles de sécurité, mais qui marchotte à peu près avec des hacks et des contournements.

Le fait que les langages de programmation aient repris cette sémantique indique bien que tout le monde considère ça comme la manière correcte de faire. Je ne connais aucun langage qui ait choisi de prendre ses arguments comme une seule grande chaîne de caractères au lieu d'une liste.
Modifié le 12/04/2024 à 22h47

Historique des modifications :

Posté le 12/04/2024 à 22h36


Le problème fondamental est que l'API Windows est fondamentalement limitée et ne permet pas de passer une liste d'arguments à un programme, uniquement une chaîne de caractères qui sera "la commande", ce qui implique tout un tas de problèmes.

Oui Rust, Java & co doivent trouver une manière correcte pour contourner cette limitation de Windows. Mais pas seulement, c'est a peu près certain qu'on trouve dans le monde des dizaines de milliers de failles à cause de cette limitation problématique et fondamentalement difficilement compatible avec la manière dont la fonction main est spécifiée dans la plupart des langages (les argument sont une liste de string, pas une string)

Encore une fois, il n'y a pas une approche qui soit meilleure qu'une autre.


Si, il y a une approche correcte et une approche fondamentalement problématique et qui encourage les failles de sécurité, mais qui marchotte à peu près avec des hacks et des contournements.

Posté le 12/04/2024 à 22h37


Le problème fondamental est que l'API Windows est fondamentalement limitée et ne permet pas de passer une liste d'arguments à un programme, uniquement une chaîne de caractères qui sera "la commande", ce qui implique tout un tas de problèmes.

Oui Rust, Java & co doivent trouver une manière correcte pour contourner cette limitation de Windows. Mais pas seulement, c'est a peu près certain qu'on trouve dans le monde des dizaines de milliers de failles à cause de cette limitation problématique et difficilement compatible avec la manière dont la fonction main est spécifiée dans la plupart des langages (les argument sont une liste de string, pas une string)

Encore une fois, il n'y a pas une approche qui soit meilleure qu'une autre.


Si, il y a une approche correcte et une approche fondamentalement problématique et qui encourage les failles de sécurité, mais qui marchotte à peu près avec des hacks et des contournements.

Posté le 12/04/2024 à 22h38


Le problème fondamental est que l'API Windows est fondamentalement limitée et ne permet pas de passer une liste d'arguments à un programme, uniquement une chaîne de caractères qui sera "la commande", ce qui implique tout un tas de problèmes.

Oui Rust, Java & co doivent trouver une manière correcte pour contourner cette limitation de Windows. Mais pas seulement, c'est a peu près certain qu'on trouve dans le monde des dizaines de milliers de failles à cause de cette limitation problématique et difficilement compatible avec la manière dont la fonction main est spécifiée dans la plupart des langages (les argument sont une liste de string, pas une string)

Encore une fois, il n'y a pas une approche qui soit meilleure qu'une autre.

Si, il y a une approche correcte et une approche fondamentalement problématique et qui encourage les failles de sécurité, mais qui marchotte à peu près avec des hacks et des contournements.

Posté le 12/04/2024 à 22h40


Le problème fondamental est que l'API Windows est fondamentalement limitée et ne permet pas de passer une liste d'arguments à un programme, uniquement une chaîne de caractères qui sera "la commande", ce qui implique tout un tas de problèmes.

Oui Rust, Java & co doivent trouver une manière correcte pour contourner cette limitation de Windows. Mais pas seulement, c'est a peu près certain qu'on trouve dans le monde des dizaines de milliers de failles à cause de cette limitation problématique et difficilement compatible avec la manière dont la fonction main est spécifiée dans la plupart des langages (les argument sont une liste de string, pas une string)

Encore une fois, il n'y a pas une approche qui soit meilleure qu'une autre.

Si, il y a une approche correcte, qui maintient la sémantique de la liste d'argument de la ligne de commande à la fonction main du programme, et une approche fondamentalement problématique et qui encourage les failles de sécurité, mais qui marchotte à peu près avec des hacks et des contournements.

Posté le 12/04/2024 à 22h41


Le problème fondamental est que l'API Windows est fondamentalement limitée et ne permet pas de passer une liste d'arguments à un programme, uniquement une chaîne de caractères qui sera "la commande", ce qui implique tout un tas de problèmes.

Oui Rust, Java & co doivent trouver une manière correcte pour contourner cette limitation de Windows. Mais pas seulement, c'est a peu près certain qu'on trouve dans le monde des dizaines de milliers de failles à cause de cette limitation problématique et difficilement compatible avec la manière dont la fonction main est spécifiée dans la plupart des langages (les argument sont une liste de string, pas une string)

Encore une fois, il n'y a pas une approche qui soit meilleure qu'une autre.

Si, il y a une approche correcte, qui maintient la sémantique de la liste d'argument de la ligne de commande à la fonction main du programme, et une approche fondamentalement problématique et qui encourage les failles de sécurité, mais qui marchotte à peu près avec des hacks et des contournements.

Posté le 12/04/2024 à 22h42


Le problème fondamental est que l'API Windows est fondamentalement limitée et ne permet pas de passer une liste d'arguments à un programme, uniquement une chaîne de caractères qui sera "la commande", ce qui implique tout un tas de problèmes.

Oui Rust, Java & co doivent trouver une manière correcte pour contourner cette limitation de Windows. Mais pas seulement, c'est a peu près certain qu'on trouve dans le monde des dizaines de milliers de failles à cause de cette limitation problématique et difficilement compatible avec la manière dont la fonction main est spécifiée dans la plupart des langages (les argument sont une liste de string, pas une string)

Encore une fois, il n'y a pas une approche qui soit meilleure qu'une autre.

Si, il y a une approche correcte, qui maintient la sémantique de la liste d'arguments, de la ligne de commande ou du script jusqu'à la fonction main du programme, et une approche fondamentalement problématique et qui encourage les failles de sécurité, mais qui marchotte à peu près avec des hacks et des contournements.

Posté le 12/04/2024 à 22h43


Le problème fondamental est que l'API Windows est fondamentalement limitée et ne permet pas de passer une liste d'arguments à un programme, uniquement une chaîne de caractères qui sera "la commande", ce qui implique tout un tas de problèmes.

Oui Rust, Java & co doivent trouver une manière correcte pour contourner cette limitation de Windows. Mais pas seulement, c'est a peu près certain qu'on trouve dans le monde des dizaines de milliers de failles à cause de cette limitation problématique et difficilement compatible avec la manière dont la fonction main est spécifiée dans la plupart des langages (les argument sont une liste de string, pas une string)

Encore une fois, il n'y a pas une approche qui soit meilleure qu'une autre.

Si, il y a une approche correcte, qui maintient la sémantique de la liste d'arguments sur toute la ligne, de la commande ou du script jusqu'à la fonction main du programme, et une approche fondamentalement problématique et qui encourage les failles de sécurité, mais qui marchotte à peu près avec des hacks et des contournements.

Posté le 12/04/2024 à 22h46


Le problème fondamental est que l'API Windows est fondamentalement limitée et ne permet pas de passer une liste d'arguments à un programme, uniquement une chaîne de caractères qui sera "la commande", ce qui implique tout un tas de problèmes.

Oui Rust, Java & co doivent trouver une manière correcte pour contourner cette limitation de Windows. Mais pas seulement, c'est a peu près certain qu'on trouve dans le monde des dizaines de milliers de failles à cause de cette limitation problématique et difficilement compatible avec la manière dont la fonction main est spécifiée dans la plupart des langages (les argument sont une liste de string, pas une string)

Encore une fois, il n'y a pas une approche qui soit meilleure qu'une autre.

Si, il y a une approche correcte, qui maintient la sémantique de la liste d'arguments sur toute la ligne, de la commande ou du script jusqu'à la fonction main du programme, et une approche fondamentalement problématique et qui encourage les failles de sécurité, mais qui marchotte à peu près avec des hacks et des contournements.

Le fait que la plupart des langages aient repris cette sémantique indique bien que tout le monde considère ça comme la manière correcte de faire. Je ne connais aucun langage qui ait choisi de prendre ses arguments comme une seule grande chaîne de caractère au lieu d'une liste.

Posté le 12/04/2024 à 22h46


Le problème est que l'API Windows est fondamentalement limitée et ne permet pas de passer une liste d'arguments à un programme, uniquement une chaîne de caractères qui sera "la commande", ce qui implique tout un tas de problèmes.

Oui Rust, Java & co doivent trouver une manière correcte pour contourner cette limitation de Windows. Mais pas seulement, c'est a peu près certain qu'on trouve dans le monde des dizaines de milliers de failles à cause de cette limitation problématique et difficilement compatible avec la manière dont la fonction main est spécifiée dans la plupart des langages (les argument sont une liste de string, pas une string)

Encore une fois, il n'y a pas une approche qui soit meilleure qu'une autre.

Si, il y a une approche correcte, qui maintient la sémantique de la liste d'arguments sur toute la ligne, de la commande ou du script jusqu'à la fonction main du programme, et une approche fondamentalement problématique et qui encourage les failles de sécurité, mais qui marchotte à peu près avec des hacks et des contournements.

Le fait que les langages de programmation aient repris cette sémantique indique bien que tout le monde considère ça comme la manière correcte de faire. Je ne connais aucun langage qui ait choisi de prendre ses arguments comme une seule grande chaîne de caractères au lieu d'une liste.

wagaf

Le problème est que l'API Windows est fondamentalement limitée et ne permet pas de passer une liste d'arguments à un programme, uniquement une chaîne de caractères qui sera "la commande", ce qui implique tout un tas de problèmes.

Oui Rust, Java & co doivent trouver une manière correcte pour contourner cette limitation de Windows. Mais pas seulement, c'est a peu près certain qu'on trouve dans le monde des dizaines de milliers de failles à cause de cette limitation problématique et difficilement compatible avec la manière dont la fonction main est spécifiée dans la plupart des langages (les argument sont une liste de string, pas une string)
Encore une fois, il n'y a pas une approche qui soit meilleure qu'une autre.

Si, il y a une approche correcte, qui maintient la sémantique de la liste d'arguments sur toute la ligne, de la commande ou du script jusqu'à la fonction main du programme, et une approche fondamentalement problématique et qui encourage les failles de sécurité, mais qui marchotte à peu près avec des hacks et des contournements.

Le fait que les langages de programmation aient repris cette sémantique indique bien que tout le monde considère ça comme la manière correcte de faire. Je ne connais aucun langage qui ait choisi de prendre ses arguments comme une seule grande chaîne de caractères au lieu d'une liste.
Oui Rust, Java & co doivent trouver une manière correcte pour contourner cette limitation de Windows. Mais pas seulement, c'est a peu près certain qu'on trouve dans le monde des dizaines de milliers de failles à cause de cette limitation problématique et difficilement compatible avec la manière dont la fonction main est spécifiée dans la plupart des langages (les argument sont une liste de string, pas une string)


Quitte à me répéter, les premières versions de l'API Windows sont sorties avant pas mal de standardisation, dont celle du C (dont le premier standard remonte à 89 rappelons le). Si les points d'entrées ont été mis à jour depuis et que Windows accepte aussi le point d'entrée actuellement répandue (autrement dit, la fonction main), beaucoup des choix techniques de l'époque ont perduré pour des raisons de compatibilité (car oui, Windows possède une API extrêmement stable, contrairement à ce que l'on trouve dans les environnements Linux). Mais pareil, il n'y a pas de solution quoi meilleure que l'autre Les deux ont des avantages et des inconvénients.

Et une fois encore, ce n'est pas une limitation de Windows. C'est une problématique entre un environnement POSIX et un non POSIX. C'est radicalement différent.
Si, il y a une approche correcte, qui maintient la sémantique de la liste d'arguments sur toute la ligne, de la commande ou du script jusqu'à la fonction main du programme, et une approche fondamentalement problématique et qui encourage les failles de sécurité, mais qui marchotte à peu près avec des hacks et des contournements.


Sérieusement ? Appeler ce qui relève d'un adapteur ou d'un wrapper un hack ? Sinon, certains langage n'avaient pas la notion de tableau d'arguments, mais bien d'une ligne : BASIC, Fortran à partir de 2003 (bon, qui propose les deux approchent :p), mais qui n'avait aucun mécanisme standard avant. Il me semble que COBOL permet les deux aussi. Et je suis loin de connaitre tous les langages.

Le problème est que tu considères la sémantique de la liste d'arguments comme allant de soi et étant une obligation aujourd'hui. Même si c'est très répandu, c'est loin d'être toujours le cas.
Le fait que les langages de programmation aient repris cette sémantique indique bien que tout le monde considère ça comme la manière correcte la plus pratique de faire.


Ma correction.

Sinon, cette faille est du même acabit qu'une injection SQL qui serait exploitable car le connecteur à la base de données, sensé protéger via l'utilisation de requêtes paramétrées, fait mal son boulot. Est-ce qu'on dit pour autant que ce sont les SGBD qui sont troués ? Ou les API qui sont mal utilisées ?

fdorin

Oui Rust, Java & co doivent trouver une manière correcte pour contourner cette limitation de Windows. Mais pas seulement, c'est a peu près certain qu'on trouve dans le monde des dizaines de milliers de failles à cause de cette limitation problématique et difficilement compatible avec la manière dont la fonction main est spécifiée dans la plupart des langages (les argument sont une liste de string, pas une string)


Quitte à me répéter, les premières versions de l'API Windows sont sorties avant pas mal de standardisation, dont celle du C (dont le premier standard remonte à 89 rappelons le). Si les points d'entrées ont été mis à jour depuis et que Windows accepte aussi le point d'entrée actuellement répandue (autrement dit, la fonction main), beaucoup des choix techniques de l'époque ont perduré pour des raisons de compatibilité (car oui, Windows possède une API extrêmement stable, contrairement à ce que l'on trouve dans les environnements Linux). Mais pareil, il n'y a pas de solution quoi meilleure que l'autre Les deux ont des avantages et des inconvénients.

Et une fois encore, ce n'est pas une limitation de Windows. C'est une problématique entre un environnement POSIX et un non POSIX. C'est radicalement différent.
Si, il y a une approche correcte, qui maintient la sémantique de la liste d'arguments sur toute la ligne, de la commande ou du script jusqu'à la fonction main du programme, et une approche fondamentalement problématique et qui encourage les failles de sécurité, mais qui marchotte à peu près avec des hacks et des contournements.


Sérieusement ? Appeler ce qui relève d'un adapteur ou d'un wrapper un hack ? Sinon, certains langage n'avaient pas la notion de tableau d'arguments, mais bien d'une ligne : BASIC, Fortran à partir de 2003 (bon, qui propose les deux approchent :p), mais qui n'avait aucun mécanisme standard avant. Il me semble que COBOL permet les deux aussi. Et je suis loin de connaitre tous les langages.

Le problème est que tu considères la sémantique de la liste d'arguments comme allant de soi et étant une obligation aujourd'hui. Même si c'est très répandu, c'est loin d'être toujours le cas.
Le fait que les langages de programmation aient repris cette sémantique indique bien que tout le monde considère ça comme la manière correcte la plus pratique de faire.


Ma correction.

Sinon, cette faille est du même acabit qu'une injection SQL qui serait exploitable car le connecteur à la base de données, sensé protéger via l'utilisation de requêtes paramétrées, fait mal son boulot. Est-ce qu'on dit pour autant que ce sont les SGBD qui sont troués ? Ou les API qui sont mal utilisées ?
Sinon, cette faille est du même acabit qu'une injection SQL qui serait exploitable car le connecteur à la base de données, sensé protéger via l'utilisation de requêtes paramétrées, fait mal son boulot. Est-ce qu'on dit pour autant que ce sont les SGBD qui sont troués ? Ou les API qui sont mal utilisées ?


Ton analogie me fait penser à « magic_quotes », qu’on ne regrettera pas : une fonctionnalité pourrie, qu’il est impossible de faire marcher correctement.

Le comportement de windows est contre-intuitif et incohérent :

quand tu es dans ton programme, les arguments sont reçues sous forme de tableau de char
* quand tu appelles CreateProcess, les arguments doivent être fournis sous forme de ligne de commande

J’ai beaucoup de choses à reprocher au modèle fork/exec de posix, mais à minima il est cohérent dans la manière dont sont passés les arguments (qui date de bien avant C89, probablement avant C K&R, donc avant 73, bien avant windows).

Rajoute à ça que CreateProcess a un comportement différent sur les fichiers bat et les fichiers exe, et que si l’appelant ne précise pas l’extension il ne sait pas ce qui va être appelé, tu as une api qui est en réalité non prévisible.

Après, la responsabilité de la faille, c’est comme on veut. MS a la responsabilité d’avoir exposé une API pourrie (ce qui peut arriver), mais surtout de ne pas l’avoir remplacée par une API plus simple / prévisible depuis tout ce temps (c’est pas comme si l’api win32 était truffée de -Ex qui remplacent une api pourrie, conservée pour la compatibilité uniquement), ce qui est moins excusable.

fdorin

Le standard sur lequel s'appuie Rust est celui de GNU C, pas "Linux" (qui est un noyau). Rien de bien nouveau, et qui à défaut d'être une norme, est un standard d'interopérabilité.

Qui plus, un vieux standard : on ne parle pas de dernière fraîcheur.

Pour être plus précis, le standard derrière s'appelle Posix (et pas GNU C). Sauf que l'API Windows existe 1985, POSIX depuis 1988. Windows ne respecte donc pas un standard qui n'existait tout simplement pas l'époque des premières versions du système d'exploitation.
Les langages doivent donc adopter un comportement particulier pour ce système d'exploitation particulier. D'aucuns dirait que c'est bien le système d'exploitation le problème.


C'est le principe de la bibliothèque standard de chaque langage. Quand tu as un langage dont l'API est proche de Posix, c'est plus simple à développer sur un système Posix que sur un système non Posix.

Le fait que plusieurs personnes se sont cassées les dents et qu'il y ait une faille à ce sujet aujourd'hui montre 2 choses :
- écrire une implémentation d'une API standard, ce n'est pas quelque chose de trivial
- un manque de documentation de l'API Windows.

Mais cela ne signifie pas que l'API présent une faille comme beaucoup le pense.

Le problème aujourd'hui touche uniquement Windows car :
- les systèmes à base de Linux (distribution classique, Android, etc.) respectent plus ou moins le standard POSIX
- les BSD respectent plus ou moins POSIX
- MacOS (basé sur BSD) respect plus ou moins POSIX

En fait, Windows est le seul système d'exploitation "grand publique" qui ne soit pas Posix de nos jours.
Une n-ème démonstration que l'habitude de M$ de produire du non-standard crée un/des risque(s) pour les utilisateurs de leurs produits


Comme dit plus haut, le standard n'existait pas à l'époque des premières versions de Windows.
[…] le standard n'existait pas à l'époque des premières versions de Windows.


Cela n'aurait rien changé qu'il existe, par l'habitude constante précédemment mentionnée de l'entreprise éditrice… problème systématique étant la véritable cause racine de la faille sujet de l'article.
Modifié le 13/04/2024 à 05h17

Historique des modifications :

Posté le 13/04/2024 à 05h16


[…] le standard n'existait pas à l'époque des premières versions de Windows.


Cela n'aurait rien changé qu'il existe, par l'habitude constante précédemment mentionnée de l'entreprise éditrice.

fdorin

Le standard sur lequel s'appuie Rust est celui de GNU C, pas "Linux" (qui est un noyau). Rien de bien nouveau, et qui à défaut d'être une norme, est un standard d'interopérabilité.

Qui plus, un vieux standard : on ne parle pas de dernière fraîcheur.

Pour être plus précis, le standard derrière s'appelle Posix (et pas GNU C). Sauf que l'API Windows existe 1985, POSIX depuis 1988. Windows ne respecte donc pas un standard qui n'existait tout simplement pas l'époque des premières versions du système d'exploitation.
Les langages doivent donc adopter un comportement particulier pour ce système d'exploitation particulier. D'aucuns dirait que c'est bien le système d'exploitation le problème.


C'est le principe de la bibliothèque standard de chaque langage. Quand tu as un langage dont l'API est proche de Posix, c'est plus simple à développer sur un système Posix que sur un système non Posix.

Le fait que plusieurs personnes se sont cassées les dents et qu'il y ait une faille à ce sujet aujourd'hui montre 2 choses :
- écrire une implémentation d'une API standard, ce n'est pas quelque chose de trivial
- un manque de documentation de l'API Windows.

Mais cela ne signifie pas que l'API présent une faille comme beaucoup le pense.

Le problème aujourd'hui touche uniquement Windows car :
- les systèmes à base de Linux (distribution classique, Android, etc.) respectent plus ou moins le standard POSIX
- les BSD respectent plus ou moins POSIX
- MacOS (basé sur BSD) respect plus ou moins POSIX

En fait, Windows est le seul système d'exploitation "grand publique" qui ne soit pas Posix de nos jours.
Une n-ème démonstration que l'habitude de M$ de produire du non-standard crée un/des risque(s) pour les utilisateurs de leurs produits


Comme dit plus haut, le standard n'existait pas à l'époque des premières versions de Windows.
Pour être plus précis, le standard derrière s'appelle Posix (et pas GNU C). Sauf que l'API Windows existe 1985, POSIX depuis 1988. Windows ne respecte donc pas un standard qui n'existait tout simplement pas l'époque des premières versions du système d'exploitation.


Ce que tu dis n'est pas possible le noyau NT n'existe que depuis juillet 1993 et ne se base justement pas sur DOS, par contre, il gare une certaines compatibilités, mais les applications Ms-DOS/Windows de l'époque ne fonctionne pas sous Windows NT.

zefling

Pour être plus précis, le standard derrière s'appelle Posix (et pas GNU C). Sauf que l'API Windows existe 1985, POSIX depuis 1988. Windows ne respecte donc pas un standard qui n'existait tout simplement pas l'époque des premières versions du système d'exploitation.


Ce que tu dis n'est pas possible le noyau NT n'existe que depuis juillet 1993 et ne se base justement pas sur DOS, par contre, il gare une certaines compatibilités, mais les applications Ms-DOS/Windows de l'époque ne fonctionne pas sous Windows NT.
En quoi n'est-ce pas possible ? Un changement majeur niveau noyau n'implique pas forcément un changement des API exposés.

Le changement de noyau NT reflétait surtout une modification profonde de la manière de communiquer avec le matériel. De manière très simpliste (car c'est loin d'être le seul changement mais sans doute le plus significatif), avant NT, les drivers pouvaient accéder directement au matériel (en by-passant complètement le noyau donc), ce qui n'est plus le cas avec NT.

Winapi, première version, date du 20 novembre 1985 d'après Wikipédia et existait bien avant le noyau NT.

Il ne faut pas confondre le noyau (qui gère entre autre le matériel) des API exposés pour les programmes.

fdorin

Le standard sur lequel s'appuie Rust est celui de GNU C, pas "Linux" (qui est un noyau). Rien de bien nouveau, et qui à défaut d'être une norme, est un standard d'interopérabilité.

Qui plus, un vieux standard : on ne parle pas de dernière fraîcheur.

Pour être plus précis, le standard derrière s'appelle Posix (et pas GNU C). Sauf que l'API Windows existe 1985, POSIX depuis 1988. Windows ne respecte donc pas un standard qui n'existait tout simplement pas l'époque des premières versions du système d'exploitation.
Les langages doivent donc adopter un comportement particulier pour ce système d'exploitation particulier. D'aucuns dirait que c'est bien le système d'exploitation le problème.


C'est le principe de la bibliothèque standard de chaque langage. Quand tu as un langage dont l'API est proche de Posix, c'est plus simple à développer sur un système Posix que sur un système non Posix.

Le fait que plusieurs personnes se sont cassées les dents et qu'il y ait une faille à ce sujet aujourd'hui montre 2 choses :
- écrire une implémentation d'une API standard, ce n'est pas quelque chose de trivial
- un manque de documentation de l'API Windows.

Mais cela ne signifie pas que l'API présent une faille comme beaucoup le pense.

Le problème aujourd'hui touche uniquement Windows car :
- les systèmes à base de Linux (distribution classique, Android, etc.) respectent plus ou moins le standard POSIX
- les BSD respectent plus ou moins POSIX
- MacOS (basé sur BSD) respect plus ou moins POSIX

En fait, Windows est le seul système d'exploitation "grand publique" qui ne soit pas Posix de nos jours.
Une n-ème démonstration que l'habitude de M$ de produire du non-standard crée un/des risque(s) pour les utilisateurs de leurs produits


Comme dit plus haut, le standard n'existait pas à l'époque des premières versions de Windows.
En terme de manque de documentation...
"To run a batch file, you must start the command interpreter; set lpApplicationName to cmd.exe and set lpCommandLine to the following arguments: /c plus the name of the batch file."

(doc de CreateProcess)

Je ne vois pas où est la partie qui dit que tu peux exécuter un bat... (ça parle d'exécuter un module)

fdorin

J'ai regardé l'article de blog cité pour plus de détails techniques.

Alors non, le problème ne vient pas de Windows. Le problème vient de la différence de comportement entre le monde Unix et le monde Windows (plus particulièrement, les caractères d'échappement à utiliser lors de la création d'un processus, où, sous Linux les spawn, popen, etc. utilise le backslash tandis que CreateProcess (l'API WIndows donc) utilise le caret (^).

Le fait que la même erreur ait été faite par plusieurs équipes distinctes dans plusieurs langages n'en fait pas un problème de Windows. Quand on appelle mal une API, il ne faut pas rejeter la faute sur l'API.

Des comportements Unix ont été calqués sur Windows, et après on vient dire que c'est de la faute de Windows. Le problème aurait tout aussi bien pu être dans l'autre sens, et là, personne n'aurait remis en cause Linux.

Comme expliqué dans l'article, on peut regretter que la communication soit axée sur Rust alors que la faille touche de nombreux langages. C'est juste que la faille a été découverte via Rust en premier.
Le problème est plus compliqué que ça.

CreateProcess, quand il lance un exécutable (fichier exe) classique est correctement échappé. Le truc, c'est que si on lui fait exécuter un fichier de commandes windows (fichier bat ou cmd), il démarre en sous-main un interpréteur cmd.exe va traiter l'ensemble des paramètres comme une ligne dans un fichier de commande avec toutes les fonctionnalité associées beaucoup trop complexes pour un simple démarrage de script. Les règles d’échappement sont différentes, mais il y a aussi le fait que les variables d’environnement, mots clé et caractères spéciaux du langage batch sont traitées ...

Alors oui on peut dire que c'est un choix de fonctionnement de Windows et Rust doit s'adapter à la situation. C'est d'ailleurs ce que Rust a fait au final, là où Java a considéré que ce n'était pas son problème. Mais pour faire cela bien dès le début, encore aurait il fallu que ce comportement ait été correctement documenté. Or la documentation MSDN actuelle de CreateProcess ne dit rien de l'utilisation implicite de cmd.exe pour l'exécution des fichiers de commande, au contraire, elle propose de lancer cmd.exe manuellement.

Il est difficile de reprocher à Rust de ne pas avoir anticipé un comportement non documenté d'une API Windows dont les conséquence sur la sécurité avaient échappé a la plupart jusqu'à présent.
Modifié le 13/04/2024 à 07h37

Historique des modifications :

Posté le 12/04/2024 à 21h00


Le problème est que c'est plus compliqué que ça.

CreateProcess, quand il lance un exécutable (fichier exe) classique est correctement échappé. Le truc, c'est que si on lui fait exécuter un fichier de commandes windows (fichier bat ou cmd), il démarre en sous-main un interpréteur cmd.exe va traiter l'ensemble des paramètres comme une ligne dans un fichier de commande avec toutes les fonctionnalité associées beaucoup trop complexes pour un simple démarrage de script (les règles d’échappement sont différentes, mais les variables d’environnement ou les mot clés du langage batch sont traitées).

Le gros du problème, c'est surtout que ce fonctionnement n'est pas clairement documenté par Microsoft, ce qui fait qu'il était difficile pour les développeurs de Rust de l'anticiper.

Posté le 13/04/2024 à 06h13


Le problème est plus compliqué que ça.

CreateProcess, quand il lance un exécutable (fichier exe) classique est correctement échappé. Le truc, c'est que si on lui fait exécuter un fichier de commandes windows (fichier bat ou cmd), il démarre en sous-main un interpréteur cmd.exe va traiter l'ensemble des paramètres comme une ligne dans un fichier de commande avec toutes les fonctionnalité associées beaucoup trop complexes pour un simple démarrage de script (les règles d’échappement sont différentes, mais les variables d’environnement ou les mot clés du langage batch sont traitées).

Le gros du problème, c'est surtout que ce fonctionnement n'est pas clairement documenté par Microsoft, ce qui fait qu'il était difficile pour les développeurs de Rust de l'anticiper.

Posté le 13/04/2024 à 07h16


Le problème est plus compliqué que ça.

CreateProcess, quand il lance un exécutable (fichier exe) classique est correctement échappé. Le truc, c'est que si on lui fait exécuter un fichier de commandes windows (fichier bat ou cmd), il démarre en sous-main un interpréteur cmd.exe va traiter l'ensemble des paramètres comme une ligne dans un fichier de commande avec toutes les fonctionnalité associées beaucoup trop complexes pour un simple démarrage de script. Les règles d’échappement sont différentes, mais il y a aussi le fait que les variables d’environnement, mots clé et caractères spéciaux du langage batch sont traitées ...

Alors on pourrait dire que Rust aurait du s'adapter à la situation, et c'est ce qu'il a fait (dans la mesure du possible), mais l'origine du problème, vient surtout du fait que ce fonctionnement n'est pas clairement documenté par Microsoft. Il est difficile de reprocher à Rust de ne pas avoir anticipé un comportement non documenté d'une API Windows.

Posté le 13/04/2024 à 07h26


Le problème est plus compliqué que ça.

CreateProcess, quand il lance un exécutable (fichier exe) classique est correctement échappé. Le truc, c'est que si on lui fait exécuter un fichier de commandes windows (fichier bat ou cmd), il démarre en sous-main un interpréteur cmd.exe va traiter l'ensemble des paramètres comme une ligne dans un fichier de commande avec toutes les fonctionnalité associées beaucoup trop complexes pour un simple démarrage de script. Les règles d’échappement sont différentes, mais il y a aussi le fait que les variables d’environnement, mots clé et caractères spéciaux du langage batch sont traitées ...

Alors oui on pourrait dire que c'est un comportement connu de Windows et Rust aurait du s'adapter à la situation, et c'est ce qu'il a fait (dans la mesure du possible), mais encore faudrait il que ce comportement ait été correctement documenté, or la documentation MSDN actuelle de CreateProcess ne dit rien de l'utilisation implicite de cmd.exe pour l'exécution des fichiers de commande, au contraire, elle propose de lancer cmd.exe manuellement.

Il est difficile de reprocher à Rust de ne pas avoir anticipé un comportement non documenté d'une API Windows dont les conséquence sur la sécurité avaient échappé a la plupart jusqu'à présent.

Posté le 13/04/2024 à 07h30


Le problème est plus compliqué que ça.

CreateProcess, quand il lance un exécutable (fichier exe) classique est correctement échappé. Le truc, c'est que si on lui fait exécuter un fichier de commandes windows (fichier bat ou cmd), il démarre en sous-main un interpréteur cmd.exe va traiter l'ensemble des paramètres comme une ligne dans un fichier de commande avec toutes les fonctionnalité associées beaucoup trop complexes pour un simple démarrage de script. Les règles d’échappement sont différentes, mais il y a aussi le fait que les variables d’environnement, mots clé et caractères spéciaux du langage batch sont traitées ...

Alors oui on pourrait dire que c'est un comportement connu de Windows et Rust aurait du s'adapter à la situation (ce qu'il a fait, là où Java a considéré que ce n'était pas son problème), mais pour l'anciciper, encore faudrait il que ce comportement ait été correctement documenté.
La documentation MSDN actuelle de CreateProcess ne dit rien de l'utilisation implicite de cmd.exe pour l'exécution des fichiers de commande, au contraire, elle propose de lancer cmd.exe manuellement. Il est difficile de reprocher à Rust de ne pas avoir anticipé un comportement non documenté d'une API Windows dont les conséquence sur la sécurité avaient échappé a la plupart jusqu'à présent.

Posté le 13/04/2024 à 07h36


Le problème est plus compliqué que ça.

CreateProcess, quand il lance un exécutable (fichier exe) classique est correctement échappé. Le truc, c'est que si on lui fait exécuter un fichier de commandes windows (fichier bat ou cmd), il démarre en sous-main un interpréteur cmd.exe va traiter l'ensemble des paramètres comme une ligne dans un fichier de commande avec toutes les fonctionnalité associées beaucoup trop complexes pour un simple démarrage de script. Les règles d’échappement sont différentes, mais il y a aussi le fait que les variables d’environnement, mots clé et caractères spéciaux du langage batch sont traitées ...

Alors oui on peut dire que c'est un choix de fonctionnement de Windows et Rust doit s'adapter à la situation. C'est d'ailleurs ce que Rust a fait au final, là où Java a considéré que ce n'était pas son problème. Mais pour faire cela bien dès le début, encore aurait il fallu que ce comportement ait été correctement documenté. Or la documentation MSDN actuelle de CreateProcess ne dit rien de l'utilisation implicite de cmd.exe pour l'exécution des fichiers de commande, au contraire, elle propose de lancer cmd.exe manuellement. Il est difficile de reprocher à Rust de ne pas avoir anticipé un comportement non documenté d'une API Windows dont les conséquence sur la sécurité avaient échappé a la plupart jusqu'à présent.

C'est d'ailleurs pour cela que dans cet article lié j'avais émis une inquiétude quant au discours axé "sécurité" autour de ce langage.

Il ne faut jamais relâcher sa vigilance et surtout ne pas reposer sur les discours de ce type et les croyances. Même si le langage est conçu avec une gestion permettant d'éviter certaines erreurs, il y a toujours des risques.

Et si j'ai cette opinion, c'est parce qu'on entend encore de nos jours le poncif éculé et mensonger de "je suis sous Linux je suis invulnérable aux malwares" (même chose pour Mac, alors qu'il y a peu une faille des processeurs M1 a été dévoilée). La sécurité IT ne repose pas sur une marque mais un ensemble de pratiques. Le problème lorsqu'on s'engouffre dans ce genre de fantasme, c'est qu'on baisse son attention.
Modifié le 12/04/2024 à 17h51

Historique des modifications :

Posté le 12/04/2024 à 17h50


C'est d'ailleurs pour cela que dans cet article lié j'avais émis une inquiétude quant au discours axé "sécurité" autour de ce langage.

Il ne faut jamais relâcher sa vigilance et ne pas reposer sur les discours de ce type et les croyances. Même si le langage est conçu avec une gestion permettant d'éviter certaines erreurs, il y a toujours des risques.

Et si j'ai cette opinion, c'est parce qu'on entend encore de nos jours le poncif éculé et mensonger de "je suis sous Linux je suis invulnérable aux malwares" (même chose pour Mac, alors qu'il y a peu une faille des processeurs M1 a été dévoilée). La sécurité IT ne repose pas sur une marque mais un ensemble de pratiques. Le problème lorsqu'on s'engouffre dans ce genre de fantasme, c'est qu'on baisse son attention.

Pour le coup le problème est aussi en C et C++. Rust en a hérité car il utilise la fonction CreateProcess de l'API Windows en C et C++. Et Rust considère que c'est un problème et l'a corrigé immédiatement alors que le problème va, semble-il, rester en l'état, au moins pour les langages Java, C et C++.

Donc même s'il n'est pas a l'abri de tous les problèmes, Rust reste clairement plus porté sur la sécurité que la majorité des langages.
Modifié le 13/04/2024 à 06h12

Historique des modifications :

Posté le 12/04/2024 à 21h38


Pour le coup le problème est aussi en C et C++. Rust en a hérité car il utilise la fonction CreateProcess de l'API Windows en C et C++. Et Rust considère que c'est un problème et l'a corrigé immédiatement alors que le problème va semble il rester en l'état, au moins dans les langages en Java, C et C++.

Donc même s'il n'est pas a l'abri de tous les problèmes, Rust reste clairement plus porté sur la sécurité que la majorité des langage.

Uther

Pour le coup le problème est aussi en C et C++. Rust en a hérité car il utilise la fonction CreateProcess de l'API Windows en C et C++. Et Rust considère que c'est un problème et l'a corrigé immédiatement alors que le problème va, semble-il, rester en l'état, au moins pour les langages Java, C et C++.

Donc même s'il n'est pas a l'abri de tous les problèmes, Rust reste clairement plus porté sur la sécurité que la majorité des langages.
La nuance de mon propos n'est pas de dire que X est mieux que Y. Mais de dire que X ou Y, peu importe, restons vigilants sur la sécurité et ne nous reposons pas sur des promesses, aussi avérées soient-elles.

D'où :
La sécurité IT ne repose pas sur une marque mais un ensemble de pratiques.


Choisir un langage apportant plus de sécurités fait donc parti de cet ensemble de pratiques. Auquel il faut ajouter la veille autour, et de ce qui est satellite. D'où mon exemple caricatural de "Linux c'est invulnérable aux malwares". Se reposer sur le discours de sécurité de Rust (que je n'ai jamais remis pas en cause, j'en suis incapable) est un risque en soit de réduire sa vigilance.

Exemple caricatural : le programme est plus sûr grace aux capacités du langage, tant mieux. Mais si la DB est exposée aux 4 vents, la sécurité est zéro pointé.

D'où, à nouveau :
La sécurité IT ne repose pas sur une marque mais un ensemble de pratiques.


Ma crainte au sujet du discours continu "Rust = plus sécurisé", c'est justement d'engendrer l'effet inverse.

SebGF

La nuance de mon propos n'est pas de dire que X est mieux que Y. Mais de dire que X ou Y, peu importe, restons vigilants sur la sécurité et ne nous reposons pas sur des promesses, aussi avérées soient-elles.

D'où :
La sécurité IT ne repose pas sur une marque mais un ensemble de pratiques.


Choisir un langage apportant plus de sécurités fait donc parti de cet ensemble de pratiques. Auquel il faut ajouter la veille autour, et de ce qui est satellite. D'où mon exemple caricatural de "Linux c'est invulnérable aux malwares". Se reposer sur le discours de sécurité de Rust (que je n'ai jamais remis pas en cause, j'en suis incapable) est un risque en soit de réduire sa vigilance.

Exemple caricatural : le programme est plus sûr grace aux capacités du langage, tant mieux. Mais si la DB est exposée aux 4 vents, la sécurité est zéro pointé.

D'où, à nouveau :
La sécurité IT ne repose pas sur une marque mais un ensemble de pratiques.


Ma crainte au sujet du discours continu "Rust = plus sécurisé", c'est justement d'engendrer l'effet inverse.
Je comprend l'idée, mais je n'adhère pas vraiment au discours qui dit qu'on apporte plus d'attention à la sécurité quand on doit tout gérer, en tout cas c'est pas du tout ce que je constate dans la pratique.

Je suis d'autant moins convaincu que la philosophie de Rust contrairement à des langages comme Java, n'est pas d'automatiser les problématiques de sécurité pour les oublier, mais d'aider à les relever et à les prendre en charge, ce qui pousse au contraire à en prendre conscience. Rust m'a poussé a m’intéresser à la sécurité plutôt que l'inverse.

Je suis beaucoup plus conscient des problèmes de sécurité, y compris dans les autres langages, depuis que j'ai appris le Rust, et pas forcément que sur ce qui touche à la mémoire, car la communauté Rust ne tend pas vraiment à se reposer uniquement sur ce qu'offre le langage. Il y a notamment déjà pas mal d'outils qui permettent d'aller bien au delà des garanties mémoire du langage. De ce que j'ai pu constater, il y a beaucoup plus d'attention aux problématiques de sécurité en général dans les communautés Rust, que dans les communautés C et C++. C'est probablement pas pour rien que le problème a été identifié en Rust alors qu'il concerne bien d'autre langages dont certains depuis bien plus longtemps.
Modifié le 13/04/2024 à 12h34

Historique des modifications :

Posté le 13/04/2024 à 10h05


Je comprend l'idée, mais je n'adhère pas vraiment au discours qui dit qu'on apporte plus d'attention à la sécurité quand on doit tout gérer, en tout cas c'est pas du tout ce que je constate dans la pratique.

Je suis d'autant moins convaincu que la philosophie de Rust contrairement à des langages comme Java, n'est pas d'automatiser les problématiques de sécurité en les cachant mais d'aider à les relever et a les prendre en charge.

La communauté Rust ne tend pas vraiment a se reposer sur ce qu'offre le langage. Elle apporte beaucoup d'attention aux problématiques de sécurité, et il y a des outils qui permettent d'aller bien au delà des garanties mémoire du langage. C'est probablement pas pour rien que le problème a été identifié en Rust alors qu'il concerne bien d'autre langages dont certains depuis bien plus longtemps.

Posté le 13/04/2024 à 10h06


Je comprend l'idée, mais je n'adhère pas vraiment au discours qui dit qu'on apporte plus d'attention à la sécurité quand on doit tout gérer, en tout cas c'est pas du tout ce que je constate dans la pratique.

Je suis d'autant moins convaincu que la philosophie de Rust contrairement à des langages comme Java, n'est pas d'automatiser les problématiques de sécurité en les cachant mais d'aider à les relever et a les prendre en charge.

La communauté Rust ne tend pas vraiment a se reposer sur ce qu'offre le langage. Elle apporte beaucoup d'attention aux problématiques de sécurité, et il y a des outils qui permettent d'aller bien au delà des garanties mémoire du langage. C'est probablement pas pour rien que le problème a été identifié en Rust alors qu'il concerne bien d'autre langages dont certains depuis bien plus longtemps.

Posté le 13/04/2024 à 10h43


Je comprend l'idée, mais je n'adhère pas vraiment au discours qui dit qu'on apporte plus d'attention à la sécurité quand on doit tout gérer, en tout cas c'est pas du tout ce que je constate dans la pratique.

Je suis d'autant moins convaincu que la philosophie de Rust contrairement à des langages comme Java, n'est pas d'automatiser les problématiques de sécurité pour les oublier, mais d'aider à les re lever et a les prendre en charge. Je dirais que je suis beaucoup plus conscients des problèmes de sécurité depuis que j'ai appris le Rust, y compris dans les autres langages, et pas que sur ce qui touche à la mémoire.

La communauté Rust ne tend pas vraiment a se reposer uniquement sur ce qu'offre le langage. Elle apporte beaucoup d'attention aux problématiques de sécurité, et il y a des outils qui permettent d'aller bien au delà des garanties mémoire du langage. C'est probablement pas pour rien que le problème a été identifié en Rust alors qu'il concerne bien d'autre langages dont certains depuis bien plus longtemps.

Posté le 13/04/2024 à 10h49


Je comprend l'idée, mais je n'adhère pas vraiment au discours qui dit qu'on apporte plus d'attention à la sécurité quand on doit tout gérer, en tout cas c'est pas du tout ce que je constate dans la pratique.

Je suis d'autant moins convaincu que la philosophie de Rust contrairement à des langages comme Java, n'est pas d'automatiser les problématiques de sécurité pour les oublier, mais d'aider à les re lever et a les prendre en charge. Je dirais que je suis beaucoup plus conscients des problèmes de sécurité depuis que j'ai appris le Rust, y compris dans les autres langages, et pas que sur ce qui touche à la mémoire.

La communauté Rust ne tend pas vraiment à se reposer uniquement sur ce qu'offre le langage. Il y a notamment déjà pas mal d'outils qui permettent d'aller bien au delà des garanties mémoire du langage. Elle apporte beaucoup d'attention aux problématiques de sécurité, en tout cas bien plus que ce que je peux voir dans les communautés C et C++. C'est probablement pas pour rien que le problème a été identifié en Rust alors qu'il concerne bien d'autre langages dont certains depuis bien plus longtemps.

Posté le 13/04/2024 à 12h33


Je comprend l'idée, mais je n'adhère pas vraiment au discours qui dit qu'on apporte plus d'attention à la sécurité quand on doit tout gérer, en tout cas c'est pas du tout ce que je constate dans la pratique.

Je suis d'autant moins convaincu que la philosophie de Rust contrairement à des langages comme Java, n'est pas d'automatiser les problématiques de sécurité pour les oublier, mais d'aider à les relever et à les prendre en charge se qui pousse au contraire à en prendre conscience. Rust m'a poussé a m’intéresser à la sécurité plutôt que l'inverse.

Je suis beaucoup plus conscient des problèmes de sécurité, y compris dans les autres langages, depuis que j'ai appris le Rust, et pas forcément que sur ce qui touche à la mémoire, car la communauté Rust ne tend pas vraiment à se reposer uniquement sur ce qu'offre le langage. Il y a notamment déjà pas mal d'outils qui permettent d'aller bien au delà des garanties mémoire du langage. De ce que j'ai pu constater, il y a beaucoup plus d'attention aux problématiques de sécurité en général dans les communautés Rust, que dans les communautés C et C++. C'est probablement pas pour rien que le problème a été identifié en Rust alors qu'il concerne bien d'autre langages dont certains depuis bien plus longtemps.

En fait, le problème provient bien de la façon dont est architecturé Windows si cette faille n'affecte que ce système. Bon, je suppose que la faille va être corrigée fissa, mais bon ...
Vive Powershell...

Les .bat .cmd, et la construction de lignes de commandes sous Windows est un enfer. Utilisant souvent des scripts pour lancer des processus, via une ligne de commande construite, j'ai souvent eu des problèmes avec les caractères d'échappement et l'encodage.
Ca m'a même value de remplacer un exe de office chargé de lancer le bon exe car IE encodait le caractère d'échappement: même Ms s'y perd...
Ça touche les langages qui ont été dev sous Unix puis portés sous Windows.

C'est bien une faille Rust (ou autre langage) le portage n'a pas su adapter les spécificités de lancement des programmes et .bat de Windows.

Ça fait une mauvaise pub pour Rust mais Windows n'en sort pas grandit non plus...
Le souci est que ça touche aussi des langages originaires Windows. Donc, sur c coup-ci, c'est davantage Windows (son système d'interprétation de commandes en fait) qui est à blâmer.

Gilbert_Gosseyn

Le souci est que ça touche aussi des langages originaires Windows. Donc, sur c coup-ci, c'est davantage Windows (son système d'interprétation de commandes en fait) qui est à blâmer.
Je n'ai vu nulle part que ça touchait des langages originaires de Windows. T'as un lien là dessus ?

DikVin

Je n'ai vu nulle part que ça touchait des langages originaires de Windows. T'as un lien là dessus ?
https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.process.start?view=net-8.0#system-diagnostics-process-start(system-string-system-string)